#pragma once

#include <iostream>
#include <queue>
#include <pthread.h>

static int gcap = 10;


namespace BlockQueueModele
{
	template<typename T> 
	class BlockQueue
	{
	private:
		bool IsFull() {return _q.size() == _cap;}
		bool IsEmpty() {return _q.empty();}

	public:
		BlockQueue(int cap = gcap):_cap(cap),_cwait_num(0),_pwait_num(0)
		{
			pthread_mutex_init(&_mutex,nullptr);
			pthread_cond_init(&_productor_cond,nullptr);
			pthread_cond_init(&_consumer_cond,nullptr);
		}


		// 生产者
		void Equeue(const T& in)
		{
			pthread_mutex_lock(&_mutex);
			// 判断队列为空为满本身就是在访问临界区  1.在临界区等待是必然的
			while(IsFull())
			{
				std::cout << "生产者进入等待..." << std::endl;
				_pwait_num++;

				// 2. 等待时，必须释放锁
				pthread_cond_wait(&_productor_cond,&_mutex); 
				// 3.返回时，线程被唤醒 && 重新申请并持有锁 (因为它会在临界区醒来)
				_pwait_num--;

				std::cout << "生产者被唤醒" << std::endl;
			}

			_q.push(in);

			// 肯定有数据
			if(_cwait_num)
			{
				std::cout << "消费者被叫醒" << std::endl;
				// 消费者有休眠的，就唤醒
				pthread_cond_signal(&_consumer_cond);
			}
			pthread_mutex_unlock(&_mutex);
		}


		// 消费者
		void Pop(T* out)
		{
			pthread_mutex_lock(&_mutex);
			while(IsEmpty())  // while循环多一次判断，避免伪唤醒问题
			{
				std::cout << "消费者进入等待..." << std::endl;
				_cwait_num++;
				pthread_cond_wait(&_consumer_cond,&_mutex);
				_cwait_num--;
				std::cout << "消费者被唤醒" << std::endl;
			}

			*out = _q.front();
			_q.pop();

			if(_pwait_num)
			{
				std::cout << "生产者被叫醒" << std::endl;
				// 生产者有休眠的，就唤醒
				pthread_cond_signal(&_productor_cond);
			}

			pthread_mutex_unlock(&_mutex);
		}

		~BlockQueue()
		{
			pthread_mutex_destroy(&_mutex);
			pthread_cond_destroy(&_productor_cond);
			pthread_cond_destroy(&_consumer_cond);
		}

	private:
		std::queue<T> _q;               //保存数据的容器--临界资源
		int _cap;                        //bq最大容量
		pthread_mutex_t _mutex;			//互斥锁
		pthread_cond_t _productor_cond; //生产者条件变量
		pthread_cond_t _consumer_cond;  //消费者条件变量

		int _cwait_num;                 //等待的消费者数量
		int _pwait_num;					//等待的生产者数量
	};
}